/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2006 Ola Bini <ola@ologix.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package asruby.runtime.callback {

import asruby.Ruby;
import asruby.runtime.CallbackFactory;
import asruby.util.JRubyClassLoader;

import flash.net.URLLoader;

public class InvocationCallbackFactory extends CallbackFactory {
    private  var type:Class;

    protected  var classLoader:JRubyClassLoader;
    private  var typePath:String;
    protected  var runtime:Ruby;

/*     private static var SUPER_CLASS: String =  p(InvocationCallback);
    private static var FAST_SUPER_CLASS: String =  p(FastInvocationCallback);
    private static var CALL_SIG: String =  sig(RubyKernel.IRUBY_OBJECT, params(Object,
            Object[], Block));
    private static var FAST_CALL_SIG: String =  sig(RubyKernel.IRUBY_OBJECT, params(
            Object, Object[]));
    private static var BLOCK_CALL_SIG: String =  sig(RubyKernel.IRUBY_OBJECT, params(
            ThreadContext, RubyKernel.IRUBY_OBJECT, IRubyObject[]));
    private static var IRUB: String =  p(RubyKernel.IRUBY_OBJECT); */
    
    
    public static var DISPATCHER_THREADCONTEXT_INDEX: int =  1;
    public static var DISPATCHER_SELF_INDEX: int =  2;
    public static var DISPATCHER_RUBYMODULE_INDEX: int =  3;
    public static var DISPATCHER_METHOD_INDEX: int =  4;
    public static var DISPATCHER_NAME_INDEX: int =  5;
    public static var DISPATCHER_ARGS_INDEX: int =  6;
    public static var DISPATCHER_CALLTYPE_INDEX: int =  7;
    public static var DISPATCHER_BLOCK_INDEX: int =  8;
    public static var DISPATCHER_RUNTIME_INDEX: int =  9;

    private static var METHOD_ARGS_INDEX: int =  2;

    public  function InvocationCallbackFactory(runtime:Ruby, type:Class, classLoader:URLLoader){
        this.type = type;
        if (classLoader instanceof JRubyClassLoader) {
            this.classLoader = JRubyClassLoader(classLoader);
        } else {
           this.classLoader  = new JRubyClassLoader(classLoader);
        }
        //this.typePath = type.toLocaleString();
        this.runtime = runtime;
        
/*         var sm: SecurityManager =  System.getSecurityManager();
        if (sm == null) {
            this.protectionDomain = type.getProtectionDomain();
        } else {
            this.protectionDomain = AccessController.doPrivileged(
                    function PrivilegedAction():new{
                        public  function run():ProtectionDomain{
                            return type.getProtectionDomain();
                        }
                    }); 
        } */
    }

/*
    private  function getReturnClass(method:String, args:Array):Class{
        return type.getMethod(method, args).getReturnType();
    }

    private  function createCtor(namePath:String):ClassWriter{
        var cw: ClassWriter =  new ClassWriter(ClassWriter.COMPUTE_MAXS);
        cw.visit(V1_4, ACC_PUBLIC + ACC_SUPER, namePath, null, SUPER_CLASS, null);
        var mv: MethodVisitor =  cw.visitMethod(ACC_PUBLIC, "", "()V", null, null);
        mv.visitCode();
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESPECIAL, SUPER_CLASS, "", "()V");
        var line: Label =  new Label();
        mv.visitLineNumber(0, line);
        mv.visitInsn(RETURN);
        mv.visitMaxs(1, 1);
        mv.visitEnd();
        return cw;
    }

    private  function createCtorDispatcher(namePath:String, switchMap:Dictionary):ClassWriter{
        var cw: ClassWriter =  new ClassWriter(ClassWriter.COMPUTE_MAXS);
        cw.visit(V1_4, ACC_PUBLIC + ACC_SUPER, namePath, null, p(Dispatcher), null);
        var mv: SkinnyMethodAdapter =  new SkinnyMethodAdapter(cw.visitMethod(ACC_PUBLIC, "", sig(Void.TYPE, params(Ruby)), null, null));
        mv.visitCode();
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESPECIAL, p(Dispatcher), "", "()V");
        var line: Label =  new Label();
        mv.visitLineNumber(0, line);
        
        // create our array
        mv.aload(0);
        mv.ldc(new Integer(MethodIndex.NAMES.size()));
        mv.newarray(T_BYTE);
        mv.putfield(p(Dispatcher), "switchTable", ci(byte[]));
        
        // for each switch value, set it into the table
        mv.aload(0);
        mv.getfield(p(Dispatcher), "switchTable", ci(byte[]));
        
        for ( var Iterator:switchIter = switchMap.keySet().iterator(); switchIter.hasNext();) {
            var switchValue: Integer =  (Integer)switchIter.next();
            mv.dup();
            
            // method index
            mv.ldc(new Integer(MethodIndex.getIndex((String)switchMap.get(switchValue))));
            // switch value is one-based, add one
            mv.ldc(switchValue);
            
            // store
            mv.barraystore();
        }
        
        // clear the extra table on stack
        mv.pop();
        
        mv.visitInsn(RETURN);
        mv.visitMaxs(1, 1);
        mv.visitEnd();
        return cw;
    }

    private  function createCtorFast(namePath:String):ClassWriter{
        var cw: ClassWriter =  new ClassWriter(ClassWriter.COMPUTE_MAXS);
        cw.visit(V1_4, ACC_PUBLIC + ACC_SUPER, namePath, null, FAST_SUPER_CLASS, null);
        var mv: MethodVisitor =  cw.visitMethod(ACC_PUBLIC, "", "()V", null, null);
        mv.visitCode();
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESPECIAL, FAST_SUPER_CLASS, "", "()V");
        var line: Label =  new Label();
        mv.visitLineNumber(0, line);
        mv.visitInsn(RETURN);
        mv.visitMaxs(1, 1);
        mv.visitEnd();
        return cw;
    }

    private  function createBlockCtor(namePath:String):ClassWriter{
        var cw: ClassWriter =  new ClassWriter(ClassWriter.COMPUTE_MAXS);
        cw.visit(V1_4, ACC_PUBLIC + ACC_SUPER, namePath, null, p(Object),
                new String[] { p(CompiledBlockCallback) });
        cw.visitField(ACC_PRIVATE | ACC_FINAL, "$scriptObject", ci(Object), null, null);
        var mv: MethodVisitor =  cw.visitMethod(ACC_PUBLIC, "", sig(Void.TYPE, params(Object)), null, null);
        mv.visitCode();
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESPECIAL, p(Object), "", "()V");
        var line: Label =  new Label();
        mv.visitLineNumber(0, line);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitVarInsn(ALOAD, 1);
        mv.visitFieldInsn(PUTFIELD, namePath, "$scriptObject", ci(Object));
        mv.visitInsn(RETURN);
        mv.visitMaxs(1, 1);
        mv.visitEnd();
        return cw;
    }

    private  function tryClass(name:String):Class{
        try {
            return classLoader.loadClass(name);
        } catch (e:Exception) {
            return null;
        }
    }

    private  function startCall(cw:ClassWriter):MethodVisitor{
        var mv: MethodVisitor =  cw.visitMethod(ACC_PUBLIC, "call", CALL_SIG, null, null);
        
        mv.visitCode();
        var line: Label =  new Label();
        mv.visitLineNumber(0, line);
        mv.visitVarInsn(ALOAD, 1);
        mv.visitTypeInsn(CHECKCAST, typePath);
        return mv;
    }

    private  function startCallS(cw:ClassWriter):MethodVisitor{
        var mv: MethodVisitor =  cw.visitMethod(ACC_PUBLIC, "call", CALL_SIG, null, null);
        
        mv.visitCode();
        var line: Label =  new Label();
        mv.visitLineNumber(0, line);
        mv.visitVarInsn(ALOAD, 1);
        checkCast(mv, IRubyObject);
        return mv;
    }

    private  function startCallFast(cw:ClassWriter):MethodVisitor{
        var mv: MethodVisitor =  cw.visitMethod(ACC_PUBLIC, "call", FAST_CALL_SIG, null, null);
        
        mv.visitCode();
        var line: Label =  new Label();
        mv.visitLineNumber(0, line);
        mv.visitVarInsn(ALOAD, 1);
        mv.visitTypeInsn(CHECKCAST, typePath);
        return mv;
    }

    private  function startDispatcher(cw:ClassWriter):MethodVisitor{
        var mv: MethodVisitor =  cw.visitMethod(ACC_PUBLIC, "callMethod", sig(IRubyObject, params(ThreadContext, IRubyObject, RubyClass, Integer.TYPE, String,
                IRubyObject[], CallType, Block)), null, null);
        
        mv.visitCode();
        var line: Label =  new Label();
        mv.visitLineNumber(0, line);
        mv.visitVarInsn(ALOAD, 2);
        mv.visitTypeInsn(CHECKCAST, typePath);
        return mv;
    }

    private  function startCallSFast(cw:ClassWriter):MethodVisitor{
        var mv: MethodVisitor =  cw.visitMethod(ACC_PUBLIC, "call", FAST_CALL_SIG, null, null);
        
        mv.visitCode();
        var line: Label =  new Label();
        mv.visitLineNumber(0, line);
        mv.visitVarInsn(ALOAD, 1);
        mv.visitTypeInsn(CHECKCAST, IRUB);
        return mv;
    }

    private  function startBlockCall(cw:ClassWriter):MethodVisitor{
        var mv: MethodVisitor =  cw.visitMethod(ACC_PUBLIC, "call", BLOCK_CALL_SIG, null, null);
        
        mv.visitCode();
        var line: Label =  new Label();
        mv.visitLineNumber(0, line);
        return mv;
    }

    protected  function endCall(cw:ClassWriter, mv:MethodVisitor, name:String):Class{
        mv.visitEnd();
        cw.visitEnd();
        var code: Array =  cw.toByteArray();
        return classLoader.defineClass(name, code, protectionDomain);
    }

    public  function getMethod(method:String):Callback{
        var mname: String =  type.getName() + "Callback$" + method + "_0";
        var mnamePath: String =  typePath + "Callback$" + method + "_0";
        synchronized (runtime.getJRubyClassLoader()) {
            var c: Class =  tryClass(mname);
            try {
                if (c == null) {
                    var signature: Array =  new Class[] { Block };
                    var ret: Class =  getReturnClass(method, signature);
                    var cw: ClassWriter =  createCtor(mnamePath);
                    var mv: MethodVisitor =  startCall(cw);
                    
                    mv.visitVarInsn(ALOAD, 3);
                    mv.visitMethodInsn(INVOKEVIRTUAL, typePath, method, sig(ret, signature));
                    mv.visitInsn(ARETURN);
                    mv.visitMaxs(1, 3);
                    c = endCall(cw, mv, mname);
                }
                var ic: InvocationCallback =  (InvocationCallback) c.newInstance();
                ic.setArity(Arity.noArguments());
                ic.setJavaName(method);
                return ic;
            } catch (e:IllegalArgumentException) {
                throw e;
            } catch (e:Exception) {
                throw new IllegalArgumentException(e.getMessage());
            }
        }
    }

    public  function getMethod(method:String, arg1:Class):Callback{
        var mname: String =  type.getName() + "Callback$" + method + "_1";
        var mnamePath: String =  typePath + "Callback$" + method + "_1";
        synchronized (runtime.getJRubyClassLoader()) {
            var c: Class =  tryClass(mname);
            try {
                var descriptor: Array =  new Class[] {arg1};
                if (c == null) {
                    var signature: Array =  new Class[] { arg1, Block };
                    var ret: Class =  getReturnClass(method, signature);
                    var cw: ClassWriter =  createCtor(mnamePath);
                    var mv: MethodVisitor =  startCall(cw);
                    
                    loadArguments(mv, METHOD_ARGS_INDEX, 1, descriptor);

                    mv.visitVarInsn(ALOAD, 3);
                    mv.visitMethodInsn(INVOKEVIRTUAL, typePath, method, sig(ret, signature));
                    mv.visitInsn(ARETURN);
                    mv.visitMaxs(3, 3);
                    c = endCall(cw, mv, mname);
                }
                var ic: InvocationCallback =  (InvocationCallback) c.newInstance();
                ic.setArity(Arity.singleArgument());
                ic.setArgumentTypes(descriptor);
                ic.setJavaName(method);
                return ic;
            } catch (e:IllegalArgumentException) {
                throw e;
            } catch (e:Exception) {
                throw new IllegalArgumentException(e.getMessage());
            }
        }
    }

    public  function getMethod(method:String, arg1:Class, arg2:Class):Callback{
        var mname: String =  type.getName() + "Callback$" + method + "_2";
        var mnamePath: String =  typePath + "Callback$" + method + "_2";
        synchronized (runtime.getJRubyClassLoader()) {
            var c: Class =  tryClass(mname);
            try {
                var descriptor: Array =  new Class[] { arg1, arg2 };
                if (c == null) {
                    var signature: Array =  new Class[] { arg1, arg2, Block };
                    var ret: Class =  getReturnClass(method, signature);
                    var cw: ClassWriter =  createCtor(mnamePath);
                    var mv: MethodVisitor =  startCall(cw);
                    
                    loadArguments(mv, METHOD_ARGS_INDEX, 2, descriptor);

                    mv.visitVarInsn(ALOAD, 3);
                    mv.visitMethodInsn(INVOKEVIRTUAL, typePath, method, sig(ret, signature));
                    mv.visitInsn(ARETURN);
                    mv.visitMaxs(4, 3);
                    c = endCall(cw, mv, mname);
                }
                var ic: InvocationCallback =  (InvocationCallback) c.newInstance();
                ic.setArity(Arity.twoArguments());
                ic.setArgumentTypes(descriptor);
                ic.setJavaName(method);
                return ic;
            } catch (e:IllegalArgumentException) {
                throw e;
            } catch (e:Exception) {
                throw new IllegalArgumentException(e.getMessage());
            }
        }
    }
    
    public  function getMethod(method:String, arg1:Class, arg2:Class, arg3:Class):Callback{
        var mname: String =  type.getName() + "Callback$" + method + "_3";
        var mnamePath: String =  typePath + "Callback$" + method + "_3";
        synchronized (runtime.getJRubyClassLoader()) {
            var c: Class =  tryClass(mname);
            try {
                var descriptor: Array =  new Class[] { arg1, arg2, arg3 }; 
                if (c == null) {
                    var signature: Array =  new Class[] { arg1, arg2, arg3, Block }; 
                    var ret: Class =  getReturnClass(method,
                            descriptor);
                    var cw: ClassWriter =  createCtor(mnamePath);
                    var mv: MethodVisitor =  startCall(cw);
                    
                    loadArguments(mv, METHOD_ARGS_INDEX, 3, descriptor);
                    
                    mv.visitVarInsn(ALOAD, 3);
                    mv.visitMethodInsn(INVOKEVIRTUAL, typePath, method, sig(ret, signature));
                    mv.visitInsn(ARETURN);
                    mv.visitMaxs(5, 3);
                    c = endCall(cw, mv, mname);
                }
                var ic: InvocationCallback =  (InvocationCallback) c.newInstance();
                ic.setArity(Arity.fixed(3));
                ic.setArgumentTypes(descriptor);
                ic.setJavaName(method);
                return ic;
            } catch (e:IllegalArgumentException) {
                throw e;
            } catch (e:Exception) {
                throw new IllegalArgumentException(e.getMessage());
            }
        }
    }

    public  function getSingletonMethod(method:String):Callback{
        var mname: String =  type.getName() + "Callback$" + method + "S0";
        var mnamePath: String =  typePath + "Callback$" + method + "S0";
        synchronized (runtime.getJRubyClassLoader()) {
            var c: Class =  tryClass(mname);
            try {
                if (c == null) {
                    var signature: Array =  new Class[] { RubyKernel.IRUBY_OBJECT, Block };
                    var ret: Class =  getReturnClass(method, signature);
                    var cw: ClassWriter =  createCtor(mnamePath);
                    var mv: MethodVisitor =  startCallS(cw);
                    mv.visitVarInsn(ALOAD, 3);
                    mv.visitMethodInsn(INVOKESTATIC, typePath, method, sig(ret, signature));
                    mv.visitInsn(ARETURN);
                    mv.visitMaxs(1, 3);
                    c = endCall(cw, mv, mname);
                }
                var ic: InvocationCallback =  (InvocationCallback) c.newInstance();
                ic.setArity(Arity.noArguments());
                ic.setJavaName(method);
                ic.setSingleton(true);
                return ic;
            } catch (e:IllegalArgumentException) {
                throw e;
            } catch (e:Exception) {
                throw new IllegalArgumentException(e.getMessage());
            }
        }
    }

    public  function getSingletonMethod(method:String, arg1:Class):Callback{
        var mname: String =  type.getName() + "Callback$" + method + "_S1";
        var mnamePath: String =  typePath + "Callback$" + method + "_S1";
        synchronized (runtime.getJRubyClassLoader()) {
            var c: Class =  tryClass(mname);
            try {
                var descriptor: Array =  new Class[] {arg1};
                if (c == null) {
                    var signature: Array =  new Class[] { RubyKernel.IRUBY_OBJECT, arg1, Block };
                    var ret: Class =  getReturnClass(method, signature);
                    var cw: ClassWriter =  createCtor(mnamePath);
                    var mv: MethodVisitor =  startCallS(cw);
                    
                    loadArguments(mv, METHOD_ARGS_INDEX, 1, descriptor);

                    mv.visitVarInsn(ALOAD, 3);
                    mv.visitMethodInsn(INVOKESTATIC, typePath, method, sig(ret, signature));
                    mv.visitInsn(ARETURN);
                    mv.visitMaxs(3, 3);
                    c = endCall(cw, mv, mname);
                }
                var ic: InvocationCallback =  (InvocationCallback) c.newInstance();
                ic.setArity(Arity.singleArgument());
                ic.setArgumentTypes(descriptor);
                ic.setJavaName(method);
                ic.setSingleton(true);
                return ic;
            } catch (e:IllegalArgumentException) {
                throw e;
            } catch (e:Exception) {
                throw new IllegalArgumentException(e.getMessage());
            }
        }
    }

    public  function getSingletonMethod(method:String, arg1:Class, arg2:Class):Callback{
        var mname: String =  type.getName() + "Callback$" + method + "_S2";
        var mnamePath: String =  typePath + "Callback$" + method + "_S2";
        synchronized (runtime.getJRubyClassLoader()) {
            var c: Class =  tryClass(mname);
            try {
                var descriptor: Array =  new Class[] {arg1, arg2};
                if (c == null) {
                    var signature: Array =  new Class[] { RubyKernel.IRUBY_OBJECT, arg1, arg2, Block };
                    var ret: Class =  getReturnClass(method, signature);
                    var cw: ClassWriter =  createCtor(mnamePath);
                    var mv: MethodVisitor =  startCallS(cw);
                    
                    loadArguments(mv, METHOD_ARGS_INDEX, 2, descriptor);
                    mv.visitVarInsn(ALOAD, 3);
                    mv.visitMethodInsn(INVOKESTATIC, typePath, method, sig(ret, signature));
                    mv.visitInsn(ARETURN);
                    mv.visitMaxs(4, 4);
                    c = endCall(cw, mv, mname);
                }
                var ic: InvocationCallback =  (InvocationCallback) c.newInstance();
                ic.setArity(Arity.twoArguments());
                ic.setArgumentTypes(descriptor);
                ic.setJavaName(method);
                ic.setSingleton(true);
                return ic;
            } catch (e:IllegalArgumentException) {
                throw e;
            } catch (e:Exception) {
                throw new IllegalArgumentException(e.getMessage());
            }
        }
    }

    public  function getSingletonMethod(method:String, arg1:Class, arg2:Class, arg3:Class):Callback{
        var mname: String =  type.getName() + "Callback$" + method + "_S3";
        var mnamePath: String =  typePath + "Callback$" + method + "_S3";
        synchronized (runtime.getJRubyClassLoader()) {
            var c: Class =  tryClass(mname);
            try {
                var descriptor: Array =  new Class[] {arg1, arg2, arg3};
                if (c == null) {
                    var signature: Array =  new Class[] { RubyKernel.IRUBY_OBJECT, arg1, arg2, arg3, Block };
                    var ret: Class =  getReturnClass(method, signature);
                    var cw: ClassWriter =  createCtor(mnamePath);
                    var mv: MethodVisitor =  startCallS(cw);
                    
                    loadArguments(mv, METHOD_ARGS_INDEX, 3, descriptor);
                    mv.visitVarInsn(ALOAD, 3);
                    mv.visitMethodInsn(INVOKESTATIC, typePath, method, sig(ret, signature));
                    mv.visitInsn(ARETURN);
                    mv.visitMaxs(5, 3);
                    c = endCall(cw, mv, mname);
                }
                var ic: InvocationCallback =  (InvocationCallback) c.newInstance();
                ic.setArity(Arity.fixed(3));
                ic.setArgumentTypes(descriptor);
                ic.setJavaName(method);
                ic.setSingleton(true);
                return ic;
            } catch (e:IllegalArgumentException) {
                throw e;
            } catch (e:Exception) {
                throw new IllegalArgumentException(e.getMessage());
            }
        }
    }

    public  function getBlockMethod(method:String):Callback{
        // TODO: This is probably BAD...
        return new ReflectionCallback(type, method, new Class[] { RubyKernel.IRUBY_OBJECT,
                RubyKernel.IRUBY_OBJECT }, false, true, Arity.fixed(2), false);
    }

    public  function getBlockCallback(method:String, scriptObject:Object):CompiledBlockCallback{
        var typeClass: Class =  scriptObject.getClass();
        var typePathString: String =  p(typeClass);
        var mname: String =  typeClass.getName() + "BlockCallback$" + method + "xx1";
        var mnamePath: String =  typePathString + "BlockCallback$" + method + "xx1";
        synchronized (classLoader) {
            var c: Class =  tryClass(mname);
            try {
                if (c == null) {
                    var cw: ClassWriter =  createBlockCtor(mnamePath);
                    var mv: MethodVisitor =  startBlockCall(cw);
                    mv.visitVarInsn(ALOAD, 0);
                    mv.visitFieldInsn(GETFIELD, mnamePath, "$scriptObject", ci(Object));
                    mv.visitTypeInsn(CHECKCAST, p(typeClass));
                    mv.visitVarInsn(ALOAD, 1);
                    mv.visitVarInsn(ALOAD, 2);
                    mv.visitVarInsn(ALOAD, 3);
                    mv.visitMethodInsn(INVOKEVIRTUAL, typePathString, method, sig(
                            RubyKernel.IRUBY_OBJECT, params(ThreadContext,
                                    RubyKernel.IRUBY_OBJECT, IRubyObject[])));
                    mv.visitInsn(ARETURN);
                    mv.visitMaxs(2, 3);
                    c = endCall(cw, mv, mname);
                }
                var ic: CompiledBlockCallback =  (CompiledBlockCallback) c.getConstructor(Object).newInstance(scriptObject);
                return ic;
            } catch (e:IllegalArgumentException) {
                throw e;
            } catch (e:Exception) {
                e.printStackTrace();
                throw new IllegalArgumentException(e.getMessage());
            }
        }
    }

    public  function getOptSingletonMethod(method:String):Callback{
        var mname: String =  type.getName() + "Callback$" + method + "_Sopt";
        var mnamePath: String =  typePath + "Callback$" + method + "_Sopt";
        synchronized (runtime.getJRubyClassLoader()) {
            var c: Class =  tryClass(mname);
            try {
                if (c == null) {
                    var signature: Array =  new Class[] { RubyKernel.IRUBY_OBJECT, IRubyObject[], Block };
                    var ret: Class =  getReturnClass(method, signature);
                    var cw: ClassWriter =  createCtor(mnamePath);
                    var mv: MethodVisitor =  startCallS(cw);
                    
                    mv.visitVarInsn(ALOAD, METHOD_ARGS_INDEX);
                    checkCast(mv, IRubyObject[]);
                    mv.visitVarInsn(ALOAD, 3);
                    mv.visitMethodInsn(INVOKESTATIC, typePath, method, sig(ret, signature));
                    mv.visitInsn(ARETURN);
                    mv.visitMaxs(2, 3);
                    c = endCall(cw, mv, mname);
                }
                var ic: InvocationCallback =  (InvocationCallback) c.newInstance();
                ic.setArity(Arity.optional());
                ic.setArgumentTypes(InvocationCallback.OPTIONAL_ARGS);
                ic.setJavaName(method);
                ic.setSingleton(true);
                return ic;
            } catch (e:IllegalArgumentException) {
                throw e;
            } catch (e:Exception) {
                throw new IllegalArgumentException(e.getMessage());
            }
        }
    }

    public  function getOptMethod(method:String):Callback{
        var mname: String =  type.getName() + "Callback$" + method + "_opt";
        var mnamePath: String =  typePath + "Callback$" + method + "_opt";
        synchronized (runtime.getJRubyClassLoader()) {
            var c: Class =  tryClass(mname);
            try {
                if (c == null) {
                    var signature: Array =  new Class[] { IRubyObject[], Block };
                    var ret: Class =  getReturnClass(method, signature);
                    var cw: ClassWriter =  createCtor(mnamePath);
                    var mv: MethodVisitor =  startCall(cw);
                    
                    mv.visitVarInsn(ALOAD, METHOD_ARGS_INDEX);
                    checkCast(mv, IRubyObject[]);
                    mv.visitVarInsn(ALOAD, 3);
                    mv.visitMethodInsn(INVOKEVIRTUAL, typePath, method, sig(ret, signature));
                    mv.visitInsn(ARETURN);
                    mv.visitMaxs(2, 3);
                    c = endCall(cw, mv, mname);
                }
                var ic: InvocationCallback =  (InvocationCallback) c.newInstance();
                ic.setArity(Arity.optional());
                ic.setArgumentTypes(InvocationCallback.OPTIONAL_ARGS);
                ic.setJavaName(method);
                return ic;
            } catch (e:IllegalArgumentException) {
                throw e;
            } catch (e:Exception) {
                throw new IllegalArgumentException(e.getMessage());
            }
        }
    }

    public  function getFastMethod(method:String):Callback{
        var mname: String =  type.getName() + "Callback$" + method + "_F0";
        var mnamePath: String =  typePath + "Callback$" + method + "_F0";
        synchronized (runtime.getJRubyClassLoader()) {
            var c: Class =  tryClass(mname);
            try {
                if (c == null) {
                    var ret: Class =  getReturnClass(method, new Class[0]);
                    var cw: ClassWriter =  createCtorFast(mnamePath);
                    var mv: MethodVisitor =  startCallFast(cw);

                    mv.visitMethodInsn(INVOKEVIRTUAL, typePath, method, sig(ret));
                    mv.visitInsn(ARETURN);
                    mv.visitMaxs(1, 3);
                    c = endCall(cw, mv, mname);
                }
                var ic: FastInvocationCallback =  (FastInvocationCallback) c.newInstance();
                ic.setArity(Arity.noArguments());
                ic.setJavaName(method);
                return ic;
            } catch (e:IllegalArgumentException) {
                throw e;
            } catch (e:Exception) {
                throw new IllegalArgumentException(e.getMessage());
            }
        }
    }

    public  function getFastMethod(method:String, arg1:Class):Callback{
        var mname: String =  type.getName() + "Callback$" + method + "_F1";
        var mnamePath: String =  typePath + "Callback$" + method + "_F1";
        synchronized (runtime.getJRubyClassLoader()) {
            var c: Class =  tryClass(mname);
            try {
                var descriptor: Array =  new Class[] { arg1 };
                if (c == null) {
                    var signature: Array =  descriptor;
                    var ret: Class =  getReturnClass(method, signature);
                    var cw: ClassWriter =  createCtorFast(mnamePath);
                    var mv: MethodVisitor =  startCallFast(cw);
                    
                    loadArguments(mv, METHOD_ARGS_INDEX, 1, descriptor);

                    mv.visitMethodInsn(INVOKEVIRTUAL, typePath, method, sig(ret, signature));
                    mv.visitInsn(ARETURN);
                    mv.visitMaxs(3, 3);
                    c = endCall(cw, mv, mname);
                }
                var ic: FastInvocationCallback =  (FastInvocationCallback) c.newInstance();
                ic.setArity(Arity.singleArgument());
                ic.setArgumentTypes(descriptor);
                ic.setJavaName(method);
                return ic;
            } catch (e:IllegalArgumentException) {
                throw e;
            } catch (e:Exception) {
                throw new IllegalArgumentException(e.getMessage());
            }
        }
    }

    public  function getFastMethod(method:String, arg1:Class, arg2:Class):Callback{
        var mname: String =  type.getName() + "Callback$" + method + "_F2";
        var mnamePath: String =  typePath + "Callback$" + method + "_F2";
        synchronized (runtime.getJRubyClassLoader()) {
            var c: Class =  tryClass(mname);
            try {
                var descriptor: Array =  new Class[] { arg1, arg2 };
                if (c == null) {
                    var signature: Array =  descriptor;
                    var ret: Class =  getReturnClass(method, signature);
                    var cw: ClassWriter =  createCtorFast(mnamePath);
                    var mv: MethodVisitor =  startCallFast(cw);
                    
                    loadArguments(mv, METHOD_ARGS_INDEX, 2, descriptor);

                    mv.visitMethodInsn(INVOKEVIRTUAL, typePath, method, sig(ret, signature));
                    mv.visitInsn(ARETURN);
                    mv.visitMaxs(4, 3);
                    c = endCall(cw, mv, mname);
                }
                var ic: FastInvocationCallback =  (FastInvocationCallback) c.newInstance();
                ic.setArity(Arity.twoArguments());
                ic.setArgumentTypes(descriptor);
                ic.setJavaName(method);
                return ic;
            } catch (e:IllegalArgumentException) {
                throw e;
            } catch (e:Exception) {
                throw new IllegalArgumentException(e.getMessage());
            }
        }
    }

    public  function getFastMethod(method:String, arg1:Class, arg2:Class, arg3:Class):Callback{
        var mname: String =  type.getName() + "Callback$" + method + "_F3";
        var mnamePath: String =  typePath + "Callback$" + method + "_F3";
        synchronized (runtime.getJRubyClassLoader()) {
            var c: Class =  tryClass(mname);
            try {
                var descriptor: Array =  new Class[] { arg1, arg2, arg3 };
                if (c == null) {
                    var signature: Array =  descriptor;
                    var ret: Class =  getReturnClass(method, signature);
                    var cw: ClassWriter =  createCtorFast(mnamePath);
                    var mv: MethodVisitor =  startCallFast(cw);
                    
                    loadArguments(mv, METHOD_ARGS_INDEX, 3, descriptor);

                    mv.visitMethodInsn(INVOKEVIRTUAL, typePath, method, sig(ret, signature));
                    mv.visitInsn(ARETURN);
                    mv.visitMaxs(5, 3);
                    c = endCall(cw, mv, mname);
                }
                var ic: FastInvocationCallback =  (FastInvocationCallback) c.newInstance();
                ic.setArity(Arity.fixed(3));
                ic.setArgumentTypes(descriptor);
                ic.setJavaName(method);
                return ic;
            } catch (e:IllegalArgumentException) {
                throw e;
            } catch (e:Exception) {
                throw new IllegalArgumentException(e.getMessage());
            }
        }
    }

    public  function getFastSingletonMethod(method:String):Callback{
        var mname: String =  type.getName() + "Callback$" + method + "_FS0";
        var mnamePath: String =  typePath + "Callback$" + method + "_FS0";
        synchronized (runtime.getJRubyClassLoader()) {
            var c: Class =  tryClass(mname);
            try {
                if (c == null) {
                    var signature: Array =  new Class[] { RubyKernel.IRUBY_OBJECT };
                    var ret: Class =  getReturnClass(method, signature);
                    var cw: ClassWriter =  createCtorFast(mnamePath);
                    var mv: MethodVisitor =  startCallSFast(cw);

                    mv.visitMethodInsn(INVOKESTATIC, typePath, method, sig(ret, signature));
                    mv.visitInsn(ARETURN);
                    mv.visitMaxs(1, 3);
                    c = endCall(cw, mv, mname);
                }
                var ic: FastInvocationCallback =  (FastInvocationCallback) c.newInstance();
                ic.setArity(Arity.noArguments());
                ic.setJavaName(method);
                ic.setSingleton(true);
                return ic;
            } catch (e:IllegalArgumentException) {
                throw e;
            } catch (e:Exception) {
                throw new IllegalArgumentException(e.getMessage());
            }
        }
    }

    public  function getFastSingletonMethod(method:String, arg1:Class):Callback{
        var mname: String =  type.getName() + "Callback$" + method + "_FS1";
        var mnamePath: String =  typePath + "Callback$" + method + "_FS1";
        synchronized (runtime.getJRubyClassLoader()) {
            var c: Class =  tryClass(mname);
            try {
                var descriptor: Array =  new Class[] {arg1};
                if (c == null) {
                    var signature: Array =  new Class[] { RubyKernel.IRUBY_OBJECT, arg1 };
                    var ret: Class =  getReturnClass(method, signature);
                    var cw: ClassWriter =  createCtorFast(mnamePath);
                    var mv: MethodVisitor =  startCallSFast(cw);
                    
                    loadArguments(mv, METHOD_ARGS_INDEX, 1, descriptor);

                    mv.visitMethodInsn(INVOKESTATIC, typePath, method, sig(ret, signature));
                    mv.visitInsn(ARETURN);
                    mv.visitMaxs(3, 3);
                    c = endCall(cw, mv, mname);
                }
                var ic: FastInvocationCallback =  (FastInvocationCallback) c.newInstance();
                ic.setArity(Arity.singleArgument());
                ic.setArgumentTypes(descriptor);
                ic.setJavaName(method);
                ic.setSingleton(true);
                return ic;
            } catch (e:IllegalArgumentException) {
                throw e;
            } catch (e:Exception) {
                throw new IllegalArgumentException(e.getMessage());
            }
        }
    }

    public  function getFastSingletonMethod(method:String, arg1:Class, arg2:Class):Callback{
        var mname: String =  type.getName() + "Callback$" + method + "_FS2";
        var mnamePath: String =  typePath + "Callback$" + method + "_FS2";
        synchronized (runtime.getJRubyClassLoader()) {
            var c: Class =  tryClass(mname);
            try {
                var descriptor: Array =  new Class[] {arg1, arg2};
                if (c == null) {
                    var signature: Array =  new Class[] { RubyKernel.IRUBY_OBJECT, arg1, arg2 };
                    var ret: Class =  getReturnClass(method, signature);
                    var cw: ClassWriter =  createCtorFast(mnamePath);
                    var mv: MethodVisitor =  startCallSFast(cw);
                    
                    loadArguments(mv, METHOD_ARGS_INDEX, 2, descriptor);

                    mv.visitMethodInsn(INVOKESTATIC, typePath, method, sig(ret, signature));
                    mv.visitInsn(ARETURN);
                    mv.visitMaxs(4, 4);
                    c = endCall(cw, mv, mname);
                }
                var ic: FastInvocationCallback =  (FastInvocationCallback) c.newInstance();
                ic.setArity(Arity.twoArguments());
                ic.setArgumentTypes(descriptor);
                ic.setJavaName(method);
                ic.setSingleton(true);
                return ic;
            } catch (e:IllegalArgumentException) {
                throw e;
            } catch (e:Exception) {
                throw new IllegalArgumentException(e.getMessage());
            }
        }
    }

    public  function getFastSingletonMethod(method:String, arg1:Class, arg2:Class, arg3:Class):Callback{
        var mname: String =  type.getName() + "Callback$" + method + "_FS3";
        var mnamePath: String =  typePath + "Callback$" + method + "_FS3";
        synchronized (runtime.getJRubyClassLoader()) {
            var c: Class =  tryClass(mname);
            try {
                var descriptor: Array =  new Class[] {arg1, arg2, arg3};
                if (c == null) {
                    var signature: Array =  new Class[] { RubyKernel.IRUBY_OBJECT, arg1, arg2, arg3 };
                    var ret: Class =  getReturnClass(method, signature);
                    var cw: ClassWriter =  createCtorFast(mnamePath);
                    var mv: MethodVisitor =  startCallSFast(cw);
                    
                    loadArguments(mv, METHOD_ARGS_INDEX, 3, descriptor);

                    mv.visitMethodInsn(INVOKESTATIC, typePath, method, sig(ret, signature));
                    mv.visitInsn(ARETURN);
                    mv.visitMaxs(5, 3);
                    c = endCall(cw, mv, mname);
                }
                var ic: FastInvocationCallback =  (FastInvocationCallback) c.newInstance();
                ic.setArity(Arity.fixed(3));
                ic.setArgumentTypes(descriptor);
                ic.setJavaName(method);
                ic.setSingleton(true);
                return ic;
            } catch (e:IllegalArgumentException) {
                throw e;
            } catch (e:Exception) {
                throw new IllegalArgumentException(e.getMessage());
            }
        }
    }

    public  function getFastOptMethod(method:String):Callback{
        var mname: String =  type.getName() + "Callback$" + method + "_Fopt";
        var mnamePath: String =  typePath + "Callback$" + method + "_Fopt";
        synchronized (runtime.getJRubyClassLoader()) {
            var c: Class =  tryClass(mname);
            try {
                if (c == null) {
                    var signature: Array =  new Class[] { IRubyObject[] };
                    var ret: Class =  getReturnClass(method, signature);
                    var cw: ClassWriter =  createCtorFast(mnamePath);
                    var mv: MethodVisitor =  startCallFast(cw);
                    
                    mv.visitVarInsn(ALOAD, METHOD_ARGS_INDEX);
                    checkCast(mv, IRubyObject[]);
                    mv.visitMethodInsn(INVOKEVIRTUAL, typePath, method, sig(ret, signature));
                    mv.visitInsn(ARETURN);
                    mv.visitMaxs(2, 3);
                    c = endCall(cw, mv, mname);
                }
                var ic: FastInvocationCallback =  (FastInvocationCallback) c.newInstance();
                ic.setArity(Arity.optional());
                ic.setArgumentTypes(InvocationCallback.OPTIONAL_ARGS);
                ic.setJavaName(method);
                return ic;
            } catch (e:IllegalArgumentException) {
                throw e;
            } catch (e:Exception) {
                throw new IllegalArgumentException(e.getMessage());
            }
        }
    }

    public  function getFastOptSingletonMethod(method:String):Callback{
        var mname: String =  type.getName() + "Callback$" + method + "_FSopt";
        var mnamePath: String =  typePath + "Callback$" + method + "_FSopt";
        synchronized (runtime.getJRubyClassLoader()) {
            var c: Class =  tryClass(mname);
            try {
                if (c == null) {
                    var signature: Array =  new Class[] { RubyKernel.IRUBY_OBJECT, IRubyObject[] };
                    var ret: Class =  getReturnClass(method, signature);
                    var cw: ClassWriter =  createCtorFast(mnamePath);
                    var mv: MethodVisitor =  startCallSFast(cw);
                    
                    mv.visitVarInsn(ALOAD, METHOD_ARGS_INDEX);
                    checkCast(mv, IRubyObject[]);
                    mv.visitMethodInsn(INVOKESTATIC, typePath, method, sig(ret, signature));
                    mv.visitInsn(ARETURN);
                    mv.visitMaxs(2, 3);
                    c = endCall(cw, mv, mname);
                }
                var ic: FastInvocationCallback =  (FastInvocationCallback) c.newInstance();
                ic.setArity(Arity.optional());
                ic.setArgumentTypes(InvocationCallback.OPTIONAL_ARGS);
                ic.setJavaName(method);
                ic.setSingleton(true);
                return ic;
            } catch (e:IllegalArgumentException) {
                throw e;
            } catch (e:Exception) {
                throw new IllegalArgumentException(e.getMessage());
            }
        }
    }
    
    public  function createDispatcher(metaClass:RubyClass):Dispatcher{
        var className: String =  type.getName() + "Dispatcher$" + metaClass.getBaseName();
        var classPath: String =  typePath + "Dispatcher$" + metaClass.getBaseName();
        
        synchronized (runtime.getJRubyClassLoader()) {
            var c: Class =  tryClass(className);
            try {
                if (c == null) {
                    // build a map of all methods from the module and all its parents
                    var allMethods: Dictionary =  new HashMap();
                    var current: RubyModule =  metaClass;
                    
                    while (current != null) {
                        for ( var Iterator:methodIter = current.getMethods().entrySet().iterator(); methodIter.hasNext();) {
                            var entry: Map.Entry =  (Map.Entry)methodIter.next();
                            
                            if (allMethods.containsKey(entry.getKey())) continue;
                            
                            var dynamicMethod: DynamicMethod =  (DynamicMethod)entry.getValue();
                            if (!(dynamicMethod instanceof JavaMethod)) {
                                // method is not a simple/fast method, don't add to our big switch
                                // FIXME: eventually, we'll probably want to add it to the switch for fast non-hash dispatching
                                continue;
                            } else {
                                // TODO: skip singleton methods for now; we'll figure out fast dispatching for them in a future patch
                                var javaMethod: JavaMethod =  (JavaMethod)dynamicMethod;
                                        
                                // singleton methods require doing a static invocation, etc...disabling again for now
                                if (javaMethod.isSingleton() || javaMethod.getCallConfig() != CallConfiguration.NO_FRAME_NO_SCOPE) continue;
                                
                                // skipping non-public methods for now, to avoid visibility checks in STI
                                if (dynamicMethod.getVisibility() != Visibility.PUBLIC) continue;
                            }
                            
                            allMethods.put(entry.getKey(), entry.getValue());
                        }
                        current = current.getSuperClass();
                        while (current != null && current.isIncluded()) current = current.getSuperClass();
                    }
                    
                    // switches are 1-based, so add one
                    var labels: Array =  new Label[allMethods.size()];
                    var defaultLabel: Label =  new Label();

                    var switchValue: int =  0;
                    var switchMap: Dictionary =  new HashMap();
                    
                    // NOTE: We sort the list of keys here to ensure they're encountered in the same order from run to run
                    // This will aid AOT compilation, since a given revision of JRuby should always generate the same
                    // sequence of method indices, and code compiled for that revision should continue to work.
                    // FIXME: This will not aid compiling once and running across JRuby versions, since method indices
                    // could be generated in a different order on a different revision (adds, removes, etc over time)
                    var methodKeys: Array =  new ArrayList(allMethods.keySet());
                    Collections.sort(methodKeys);
                    for ( var Iterator:methodIter = methodKeys.iterator(); methodIter.hasNext();) {
                        var indexKey: String =  (String)methodIter.next();
                        switchValue++;
                        
                        switchMap.put(new Integer(switchValue), indexKey);
                        // switches are one-based, so subtract one
                        labels[switchValue - 1] = new Label();
                    }

                    var cw: ClassWriter =  createCtorDispatcher(classPath, switchMap);
                    var mv: SkinnyMethodAdapter =  new SkinnyMethodAdapter(startDispatcher(cw));
                    
                    // store runtime
                    mv.aload(DISPATCHER_THREADCONTEXT_INDEX);
                    mv.invokevirtual(p(ThreadContext), "getRuntime", sig(Ruby));
                    mv.astore(DISPATCHER_RUNTIME_INDEX);
                    
                    var tryBegin: Label =  new Label();
                    var tryEnd: Label =  new Label();
                    var tryCatch: Label =  new Label();
                    mv.trycatch(tryBegin, tryEnd, tryCatch, p(StackOverflowError));
                    mv.label(tryBegin);
                    
                    // invoke directly

                    // receiver is already loaded by startDispatcher
                    
                    // check if tracing is on
                    mv.aload(DISPATCHER_RUNTIME_INDEX);
                    mv.invokevirtual(p(Ruby), "hasEventHooks", sig(boolean));
                    mv.ifne(defaultLabel);
                    
                    // if no switch values, go straight to default
                    if (switchValue == 0) {
                        mv.go_to(defaultLabel);
                    } else {
                        // load switch value
                        mv.aload(0);
                        mv.getfield(p(Dispatcher), "switchTable", ci(byte[]));
                        
                        // ensure size isn't too large
                        mv.dup();
                        mv.arraylength();
                        mv.iload(DISPATCHER_METHOD_INDEX);
                        var ok: Label =  new Label();
                        mv.if_icmpgt(ok);
                        
                        // size is too large, remove extra table and go to default
                        mv.pop();
                        mv.go_to(defaultLabel);
                        
                        // size is ok, retrieve from table and switch on the result
                        mv.label(ok);
                        mv.iload(DISPATCHER_METHOD_INDEX);
                        mv.barrayload();
                        
                        // perform switch
                        mv.tableswitch(1, switchValue, defaultLabel, labels);
                        
                        for ( var int:i = 0; i < labels.length; i++) {
                            var rubyName: String =  (String)switchMap.get(new Integer(i + 1));
                            var dynamicMethod: DynamicMethod =  (DynamicMethod)allMethods.get(rubyName);
                            
                            mv.label(labels[i]);
    
                            // based on the check above, it's a fast method, we can fast dispatch
                            var javaMethod: JavaMethod =  (JavaMethod)dynamicMethod;
                            var method: String =  javaMethod.getJavaName();
                            var arity: Arity =  javaMethod.getArity();
                            var descriptor: Array =  javaMethod.getArgumentTypes();
                            
                            // arity check
                            checkArity(mv, arity);
                            
                            // if singleton load self/recv
                            if (javaMethod.isSingleton()) {
                                mv.aload(DISPATCHER_SELF_INDEX);
                            }
                            
                            var contextProvided: Boolean =  descriptor.length > 0 && descriptor[0] == ThreadContext;

                            if (contextProvided) {
                                mv.aload(DISPATCHER_THREADCONTEXT_INDEX);
                            }

                            var argCount: int =  arity.getValue();
                            switch (argCount) {
                            case 3: case 2: case 1:
                                loadArguments(mv, DISPATCHER_ARGS_INDEX, argCount, descriptor, contextProvided);
                                break;
                            case 0:
                                break;
                            default: // this should catch all opt/rest cases
                                mv.aload(DISPATCHER_ARGS_INDEX);
                                checkCast(mv, IRubyObject[]);
                                break;
                            }
                            
                            var ret: Class =  getReturnClass(method, descriptor);
                            var callSig: String =  sig(ret, descriptor);
//                            if (method.equals("op_equal")) System.out.println("NAME: " + type.getName() + "METHOD: " + sig(ret,descriptor) + ", ARITY: " + arity.getValue());
                            // if block, pass it
                            if (descriptor.length > 0 && descriptor[descriptor.length - 1] == Block) {
                                mv.aload(DISPATCHER_BLOCK_INDEX);
                            }
                            
                            mv.invokevirtual(typePath, method, callSig);
                            mv.areturn();
                        }
                    }
                    
                    // done with cases, handle default case by getting method object and invoking it
                    mv.label(defaultLabel);
                    var afterCall: Label =  new Label();
                    
                    dispatchWithoutSTI(mv, afterCall);

                    mv.label(tryEnd);
                    mv.go_to(afterCall);

                    mv.label(tryCatch);
                    mv.aload(DISPATCHER_RUNTIME_INDEX);
                    mv.ldc("stack level too deep");
                    mv.invokevirtual(p(Ruby), "newSystemStackError", sig(RaiseException, params(String)));
                    mv.athrow();

                    // calls done, return
                    mv.label(afterCall);
                    
                    mv.areturn();
                    mv.visitMaxs(1, 1);
                    c = endCall(cw, mv, className);
                }
                var dispatcher: Dispatcher =  (Dispatcher)c.getConstructor(new Class[] {Ruby}).newInstance(new Object[] {runtime});
                return dispatcher;
            } catch (e:IllegalArgumentException) {
                throw e;
            } catch (e:Exception) {
                e.printStackTrace();
                throw new IllegalArgumentException(e.getMessage());
            }
        }
    }
    
    private  function dispatchWithoutSTI(mv:SkinnyMethodAdapter, afterCall:Label):void{
        // retrieve method
        mv.aload(DISPATCHER_RUBYMODULE_INDEX); // module
        mv.aload(DISPATCHER_NAME_INDEX); // name
        mv.invokevirtual(p(RubyModule), "searchMethod", sig(DynamicMethod, params(String)));

        var okCall: Label =  new Label();
        
        callMethodMissingIfNecessary(mv, afterCall, okCall);

        // call is ok, punch it!
        mv.label(okCall);

        // method object already present, push various args
        mv.aload(DISPATCHER_THREADCONTEXT_INDEX); // tc
        mv.aload(DISPATCHER_SELF_INDEX); // self
        mv.aload(DISPATCHER_RUBYMODULE_INDEX); // klazz
        mv.aload(DISPATCHER_NAME_INDEX); // name
        mv.aload(DISPATCHER_ARGS_INDEX);
        mv.aload(DISPATCHER_BLOCK_INDEX);
        mv.invokevirtual(p(DynamicMethod), "call",
                sig(IRubyObject, 
                params(ThreadContext, IRubyObject, RubyModule, String, IRubyObject[], Block)));
    }
    
    public  function callMethodMissingIfNecessary(mv:SkinnyMethodAdapter, afterCall:Label, okCall:Label):void{
        var methodMissing: Label =  new Label();

        // if undefined, branch to method_missing
        mv.dup();
        mv.invokevirtual(p(DynamicMethod), "isUndefined", sig(boolean));
        mv.ifne(methodMissing);

        // if we're not attempting to invoke method_missing and method is not visible, branch to method_missing
        mv.aload(DISPATCHER_NAME_INDEX);
        mv.ldc("method_missing");
        // if it's method_missing, just invoke it
        mv.invokevirtual(p(String), "equals", sig(boolean, params(Object)));
        mv.ifne(okCall);
        // check visibility
        mv.dup(); // dup method
        mv.aload(DISPATCHER_THREADCONTEXT_INDEX);
        mv.invokevirtual(p(ThreadContext), "getFrameSelf", sig(IRubyObject));
        mv.aload(DISPATCHER_CALLTYPE_INDEX);
        mv.invokevirtual(p(DynamicMethod), "isCallableFrom", sig(boolean, params(IRubyObject, CallType)));
        mv.ifne(okCall);

        // invoke callMethodMissing
        mv.label(methodMissing);

        mv.aload(DISPATCHER_THREADCONTEXT_INDEX); // tc
        mv.swap(); // under method
        mv.aload(DISPATCHER_SELF_INDEX); // self
        mv.swap(); // under method
        mv.aload(DISPATCHER_NAME_INDEX); // name
        mv.aload(DISPATCHER_ARGS_INDEX); // args

        // caller
        mv.aload(DISPATCHER_THREADCONTEXT_INDEX);
        mv.invokevirtual(p(ThreadContext), "getFrameSelf", sig(IRubyObject));

        mv.aload(DISPATCHER_CALLTYPE_INDEX); // calltype
        mv.aload(DISPATCHER_BLOCK_INDEX); // block

        // invoke callMethodMissing method directly
        // TODO: this could be further optimized, since some DSLs hit method_missing pretty hard...
        mv.invokestatic(p(RuntimeHelpers), "callMethodMissing", sig(IRubyObject, 
                params(ThreadContext, IRubyObject, DynamicMethod, String, 
                                    IRubyObject[], IRubyObject, CallType, Block)));
        // if no exception raised, jump to end to leave result on stack for return
        mv.go_to(afterCall);
    }
    
    private  function loadArguments(mv:MethodVisitor, argsIndex:int, count:int, types:Array):void{
        loadArguments(mv, argsIndex, count, types, false);
    }
    
    private  function loadArguments(mv:MethodVisitor, argsIndex:int, count:int, types:Array, contextProvided:Boolean):void{
        for ( var int:i = 0; i < count; i++) {
            loadArgument(mv, argsIndex, i, types[i + (contextProvided ? 1 : 0)]);
        }
    }
    
    private  function loadArgument(mv:MethodVisitor, argsIndex:int, argIndex:int, type1:Class):void{
        mv.visitVarInsn(ALOAD, argsIndex);
        mv.visitLdcInsn(new Integer(argIndex));
        mv.visitInsn(AALOAD);
        checkCast(mv, type1);
    }

    private  function checkCast(mv:MethodVisitor, clazz:Class):void{
        mv.visitTypeInsn(CHECKCAST, p(clazz));
    }

    private  function checkArity(mv:SkinnyMethodAdapter, arity:Arity):void{
        if (arity.getValue() >= 0) {
            var arityOk: Label =  new Label();
            // check arity
            mv.aload(6);
            mv.arraylength();
            
            // load arity for check
            switch (arity.getValue()) {
            case 3: mv.iconst_3(); break;
            case 2: mv.iconst_2(); break;
            case 1: mv.iconst_1(); break;
            case 0: mv.iconst_0(); break;
            default: mv.ldc(new Integer(arity.getValue()));
            }
   
            mv.if_icmpeq(arityOk);
            
            // throw
            mv.aload(9);
            mv.aload(6);
            mv.arraylength();

            // load arity for error
            switch (arity.getValue()) {
            case 3: mv.iconst_3(); break;
            case 2: mv.iconst_2(); break;
            case 1: mv.iconst_1(); break;
            case 0: mv.iconst_0(); break;
            default: mv.ldc(new Integer(arity.getValue()));
            }

            mv.invokevirtual(p(Ruby), "newArgumentError", sig(RaiseException, params(int, int)));
            mv.athrow();

            // arity ok, continue
            mv.label(arityOk);
        }
    }
   */
   
   public static function p(n:Class):String {
        return null;
    }
} //InvocationCallbackFactory
}